#include <stdio.h>
#include <string.h>
#include <time.h>
#include "unittest.h"

int unittestlib_loaded = 1;

///////////////////////////////////////////////////////////////////////////////
// TestCase
///////////////////////////////////////////////////////////////////////////////
TestCase::TestCase()
  : name_("")
{
}

TestCase::TestCase(const char* name)
  : name_(name)
{
}

TestCase::~TestCase()
{
}

void TestCase::Run(TestResult* result)
{
  FunctorRegistry reginit; // defined init scope
  StartTest(result);
  try {
    SetUp();
    try {
      RunTest();
    } catch (const FailException& e) {
      result->AddFailure(this, e);
    } catch (const ErrorException& e) {
      result->AddError(this, e);
    } catch (...) {
      result->AddError(this, ErrorException(__FILE__, __LINE__,
                                            "uncaught std::exception"));
    }
    try {
      TearDown();
    } catch (...) {
      result->AddError(this, ErrorException(__FILE__, __LINE__,
                                            "TearDown() failed"));
    }
  } catch (...) {
    result->AddError(this, ErrorException(__FILE__, __LINE__,
                                          "SetUp() faild"));
  }
  EndTest(result);
}

int TestCase::CountTestCases() const
{
  return 1;
}

const char* TestCase::GetName() const
{
  return name_;
}

void TestCase::SetUp()
{
}

void TestCase::TearDown()
{
}

void TestCase::StartTest(TestResult* result)
{
  result->StartTest(this);
}

void TestCase::EndTest(TestResult* result)
{
  result->EndTest(this);
}

///////////////////////////////////////////////////////////////////////////////
// TestSuite
///////////////////////////////////////////////////////////////////////////////
TestSuite::TestSuite(const char* name)
  : name_(name)
{
}

TestSuite::~TestSuite()
{
  for (std::list<Test*>::iterator it = tests_.begin();
       it != tests_.end(); it++) {
    delete *it;
  }
}

void TestSuite::Run(TestResult* result)
{
  for (std::list<Test*>::iterator it = tests_.begin();
       it != tests_.end(); it++) {
    (*it)->Run(result);
  }
}

int TestSuite::CountTestCases() const
{
  int count = 0;
  for (std::list<Test*>::const_iterator it = tests_.begin();
       it != tests_.end(); it++) {
    count += (*it)->CountTestCases();
  }
  return count;
}

const char* TestSuite::GetName() const
{
  return name_;
}

void TestSuite::AddTest(Test* test)
{
  tests_.push_back(test);
}

///////////////////////////////////////////////////////////////////////////////
// TestRepeater
///////////////////////////////////////////////////////////////////////////////
TestRepeater::TestRepeater(Test* test, int repeat)
  : test_(test), repeat_(repeat)
{
}

TestRepeater::~TestRepeater()
{
  delete test_;
}

void TestRepeater::Run(TestResult* result)
{
  for (int i = 0; i < repeat_; i++) test_->Run(result);
}

int TestRepeater::CountTestCases() const
{
  return test_->CountTestCases() * repeat_;
}

const char* TestRepeater::GetName() const
{
  return test_->GetName();
}

///////////////////////////////////////////////////////////////////////////////
// TestRunner
///////////////////////////////////////////////////////////////////////////////

// weak symbol
void (*__hook_before_run)(void) __attribute__ ((weak));

TestRunner::TestRunner()
  : wait_(false), verbose_(false), logfile_(0)
{
}

TestRunner::~TestRunner()
{
}

int TestRunner::Run(int argc, char** argv)
{
  if (ParseArg(argc, argv)) {
    PrintUsage(argv[0]);
    return -1;
  }

  int result;
  if ((result = RunTest()) < 0) {
    PrintUsage(argv[0]);
    return -1;
  }
  if (wait_) Wait();
  return result;
}

void TestRunner::AddTest(Test* test)
{
  AddTest(test->GetName(), test);
}

void TestRunner::AddTest(const char* name, Test* test)
{
  manualRegistryTests_.AddTest(name, test);
}

int TestRunner::ParseArg(int argc, char** argv)
{
  for (int i = 1; i < argc; i++) {
    const char *arg = argv[i];
    if (!strlen(arg)) continue;
    if (*arg == '-') {
      if (!strcmp(arg, "-help")) {
        // not implement
      }
      if (!strcmp(arg, "-wait")) {
        wait_ = true; continue;
      }
      if (!strcmp(arg, "-verbose")) {
        verbose_ = true; continue;
      }
      if (!strcmp(arg, "-log")) {
        if (++i >= argc) return -1;
        logfile_ = argv[i]; continue;
      }
      fprintf(stderr, "[ERROR] illigal option: %s\n", arg);
      return -1;
    }
    if (!SetTest(arg)) {
      return -1;
    }
  }
  if (!manualRegistryTests_.HasAvailable()) {
    manualRegistryTests_.SetAllAvailable();
  }
  return 0;
}

bool TestRunner::SetTest(const char* name)
{
  if (manualRegistryTests_.SetAvailable(name)) {
    return true;
  }
  if (TestFactoryRegistry::IsAvailable(name)) {
    autoRegistryTests_.insert(name);
    return true;
  }
  fprintf(stderr, "[ERROR] test %s not found.\n", name);
  return false;
}

int TestRunner::RunTest()
{
  SmartPtr<TestListener> listener(verbose_
                                   ? (TestListener*)new Logger(stdout)
                                   : (TestListener*)new SimpleListener());
  SmartPtr<Logger> logger;
  if (logfile_) {
    logger.Reset(Logger::OpenFile(logfile_));
    if (logger.Get() == 0) {
      fprintf(stderr, "ERROR!! cannot open file: %s\n", logfile_);
      return -1;
    }
  }

  TestResult result;
  result.AddListener(listener.Get());
  if (logger.Get() != 0) {
    result.AddListener(logger.Get());
    logger->PrintHeader();
  }

  if (__hook_before_run) __hook_before_run();
  if (manualRegistryTests_.HasAvailable()) {
    manualRegistryTests_.Run(&result);
  } else {
    if (autoRegistryTests_.size() > 0) {
      for (std::set<const char*>::iterator it = autoRegistryTests_.begin();
           it != autoRegistryTests_.end(); it++) {
        TestFactoryRegistry::GetRegistry((*it))->MakeTest()->Run(&result);
      }
    } else {
      TestFactoryRegistry::GetRegistry("default")->MakeTest()->Run(&result);
    }
  }

  if (!verbose_) {
    fputc('\n', stderr);
    fflush(stderr);
  }
  PrintResult(&result);
  if (logger.Get()) logger->PrintResult(&result);

  if (result.ErrorCount() > 0) return 2;
  if (result.FailureCount() > 0) return 1;
  return 0;
}

void TestRunner::Wait() const
{
  fprintf(stderr, "\n<Enter> to continue");
  getchar();
}

void TestRunner::PrintUsage(const char* command) const
{
  fprintf(stderr, "Usage: %s [-wait] [-verbose] [-log file] [tests...]\n",
          TestUtil::TrimFileName(command));
}

void TestRunner::PrintResult(const TestResult* result) const
{
  TestUtil::PrintResult(stdout, result);
  if (verbose_) return;
  int count;
  count = result->Failures().size();
  if (count != 0) {
    printf("There were %d failures:\n", count);
    int index = 0;
    for (std::list<TestFailure*>::const_iterator it =
           result->Failures().begin();
         it != result->Failures().end(); it++) {
      fprintf(stdout, "%d) %s", ++index, (*it)->FailedTest()->GetName());
      TestUtil::PrintFailureDetail(stdout, (*it));
    }
  }
  count = result->Errors().size();
  if (count != 0) {
    printf("There were %d errors:\n", count);
    int index = 0;
    for (std::list<TestFailure*>::const_iterator it =
           result->Errors().begin();
         it != result->Errors().end(); it++) {
      fprintf(stdout, "%d) %s", ++index, (*it)->FailedTest()->GetName());
      TestUtil::PrintFailureDetail(stdout, (*it));
    }
  }
}

///////////////////////////////////////////////////////////////////////////////
// FunctorRegistry
///////////////////////////////////////////////////////////////////////////////
std::vector<FunctorContainer*> FunctorRegistry::registry_;

FunctorRegistry::FunctorRegistry()
{
  Clear();
}

FunctorRegistry::~FunctorRegistry()
{
  Clear();
}

void FunctorRegistry::Clear()
{
  for (std::vector<FunctorContainer*>::iterator it =
         registry_.begin(); it != registry_.end(); it++) {
    delete *it;
  }
  registry_.clear();
}

void FunctorRegistry::Push(const char* key, Functor* functor, int opt)
{
  registry_.push_back(new FunctorContainer(key, functor, opt));
}

FunctorContainer* FunctorRegistry::Pop(const char* key)
{
  FunctorContainer* fc = NULL;
  for (std::vector<FunctorContainer*>::reverse_iterator rit =
         registry_.rbegin(); rit != registry_.rend(); rit++) {
    if (!strcmp((*rit)->key_, key)) {
      fc = *rit;
      registry_.erase((++rit).base());
      break;
    }
  }
  return fc;
}

void FunctorRegistry::Remove(const char *key)
{
  for (std::vector<FunctorContainer*>::iterator it =
         registry_.begin(); it != registry_.end(); it++) {
    if (!strcmp((*it)->key_, key)) {
      delete *it;
      registry_.erase(it);
    }
  }
}

void *FunctorRegistry::Call(const char *key, void *retval, int argnum, ...)
{
  va_list va;
  FunctorContainer* fc;
  int mode;

  do {
    if (!(fc = Pop(key))) break;
    mode = fc->mode_;
    va_start(va, argnum);
    retval = (*fc->functor_)(argnum, va);
    va_end(va);
    delete fc;
  } while(mode == FunctorContainer::MODE_COMBINE);

  return retval;
}

///////////////////////////////////////////////////////////////////////////////
// Exception
///////////////////////////////////////////////////////////////////////////////
Exception::Exception(const char* fileName, int line, const char* message)
  : fileName_(fileName), line_(line), message_(message)
{
}

Exception::~Exception() throw()
{
}

#if 0
Exception::Exception(const Exception& rhs)
  : std::exception(rhs), fileName_(rhs.FileName()), line_(rhs.Line()),
    message_(rhs.Message())
{
}

Exception& Exception::operation=(const Exception& rhs)
{
  if (&rhs != this) {
    fileName_ = rhs.FileName();
    line_ = rhs.Line();
    message_ = rhs.Message();
  }
  return *this;
}
#endif

const char* Exception::FileName() const
{
  return fileName_;
}

int Exception::Line() const
{
  return line_;
}

const char *Exception::Message() const
{
  return message_.c_str();
}

///////////////////////////////////////////////////////////////////////////////
// FailException
///////////////////////////////////////////////////////////////////////////////
FailException::FailException(const char* fileName, int line,
                             const char* message)
  : Exception(fileName, line, message)
{
}

FailException::~FailException() throw()
{
}

///////////////////////////////////////////////////////////////////////////////
// ErrorException
///////////////////////////////////////////////////////////////////////////////
ErrorException::ErrorException(const char* fileName, int line,
                               const char* message)
  : Exception(fileName, line, message)
{
}

ErrorException::~ErrorException() throw()
{
}

///////////////////////////////////////////////////////////////////////////////
// Asserter
///////////////////////////////////////////////////////////////////////////////
void Asserter::Fail(const char* fileName, int line, const char* message,
                    bool failed)
{
  if (failed) throw FailException(fileName, line, message);
}

void Asserter::Error(const char* fileName, int line, const char* message)
{
  throw ErrorException(fileName, line, message);
}

void Asserter::Equal(const char* fileName, int line, int expected, int actual)
{
  if (expected != actual) {
    char message[80];
    memset(message, 0, sizeof(message));
    sprintf(message, "expected:<%d> but was:<%d>", expected, actual);
    Fail(fileName, line, message);
  }
}

void Asserter::Equal(const char* fileName, int line, long expected,long actual)
{
  if (expected != actual) {
    char message[80];
    memset(message, 0, sizeof(message));
    sprintf(message, "expected:<%ld> but was:<%ld>", expected, actual);
    Fail(fileName, line, message);
  }
}

void Asserter::Equal(const char* fileName, int line, const char* expected,
                     const char* actual)
{
  if (expected == actual) return;
  if (expected && actual && !strcmp(expected, actual)) return;
  if (!expected) expected = "(null)";
  if (!actual) actual = "(null)";
  std::string message;
  message.append("expected:<").append(expected).append("> but was:<").
    append(actual).append(">");
  Fail(fileName, line, message.c_str());
}

///////////////////////////////////////////////////////////////////////////////
// TestFactoryRegistry
///////////////////////////////////////////////////////////////////////////////
TestFactoryRegistry::RegistryMap::RegistryMap()
{
}

TestFactoryRegistry::RegistryMap::~RegistryMap()
{
  for (std::map<std::string, TestFactoryRegistry*>::iterator it =
         registries_.begin(); it != registries_.end(); it++) {
    delete (*it).second;
  }
}

TestFactoryRegistry*
TestFactoryRegistry::RegistryMap::GetRegistry(const char* name, bool create)
{
  return GetInstance()->GetInternalRegistry(name, create);
}

TestFactoryRegistry::RegistryMap*
TestFactoryRegistry::RegistryMap::GetInstance()
{
  static TestFactoryRegistry::RegistryMap instance;
  return &instance;
}

TestFactoryRegistry*
TestFactoryRegistry::RegistryMap::GetInternalRegistry(const char* name,
                                                      bool create)
{
  std::map<std::string, TestFactoryRegistry*>::iterator it =
    registries_.find(name);
  if (it == registries_.end()) {
    if (create) {
      TestFactoryRegistry *registry = new TestFactoryRegistry(name);
      registries_[name] = registry;
      return registry;
    }
    return 0;
  }
  return (*it).second;
}

TestFactoryRegistry::TestFactoryRegistry(const char* name)
  : name_(name)
{
}

TestFactoryRegistry::~TestFactoryRegistry()
{
}

Test* TestFactoryRegistry::MakeTest()
{
  TestSuite *suite = new TestSuite(name_);
  for (std::set<TestFactory*>::iterator it = factories_.begin();
       it != factories_.end(); it++) {
    suite->AddTest((*it)->MakeTest());
  }
  return suite;
}

TestFactoryRegistry* TestFactoryRegistry::GetRegistry(const char* name)
{
  return RegistryMap::GetRegistry(name, true);
}

void TestFactoryRegistry::RegistFactory(TestFactory* factory)
{
  factories_.insert(factory);
}

void TestFactoryRegistry::UnRegistFactory(TestFactory* factory)
{
  factories_.erase(factory);
}

bool TestFactoryRegistry::IsAvailable(const char* name)
{
  return (RegistryMap::GetRegistry(name, false) ? true : false);
}

///////////////////////////////////////////////////////////////////////////////
// TestObjectRegistry
///////////////////////////////////////////////////////////////////////////////
TestObjectRegistry::TestObjectRegistry()
{
}

TestObjectRegistry::~TestObjectRegistry()
{
  for (std::list<Entry*>::iterator it = entries_.begin();
       it != entries_.end(); it++) {
    delete (*it);
  }
}

void TestObjectRegistry::AddTest(const char* name, Test* test)
{
  entries_.push_back(new Entry(name, test));
}

bool TestObjectRegistry::SetAvailable(const char* name, bool value)
{
  bool result = false;
  for (std::list<Entry*>::iterator it = entries_.begin();
       it != entries_.end(); it++) {
    if (!strcmp((*it)->GetName(), name)) {
      result = true;
      (*it)->SetAvailable(value);
    }
  }
  return result;
}

void TestObjectRegistry::SetAllAvailable(bool value)
{
  for (std::list<Entry*>::iterator it = entries_.begin();
       it != entries_.end(); it++) {
    (*it)->SetAvailable(value);
  }
}

bool TestObjectRegistry::HasAvailable() const
{
  for (std::list<Entry*>::const_iterator it = entries_.begin();
       it != entries_.end(); it++) {
    if ((*it)->IsAvailable()) return true;
  }
  return false;
}

void TestObjectRegistry::Run(TestResult* result)
{
  for (std::list<Entry*>::iterator it = entries_.begin();
       it != entries_.end(); it++) {
    if ((*it)->IsAvailable()) {
      (*it)->GetTest()->Run(result);
      if (result->ShouldStop()) break;
    }
  }
}

TestObjectRegistry::Entry::Entry(const char* name, Test* test, bool available)
  : name_(name), test_(test), available_(available)
{
}

TestObjectRegistry::Entry::~Entry()
{
  delete test_;
}

const char* TestObjectRegistry::Entry::GetName() const
{
  return name_.c_str();
}

Test* TestObjectRegistry::Entry::GetTest() const
{
  return test_;
}

bool TestObjectRegistry::Entry::IsAvailable() const
{
  return available_;
}

void TestObjectRegistry::Entry::SetAvailable(bool value)
{
  available_ = value;
}

///////////////////////////////////////////////////////////////////////////////
// TestResult
///////////////////////////////////////////////////////////////////////////////
TestResult::TestResult()
  : runCount_(0), shouldStop_(0)
{
}

TestResult::~TestResult()
{
  for (std::list<TestFailure*>::iterator it = failures_.begin();
       it != failures_.end(); it++) {
    delete (*it);
  }
  for (std::list<TestFailure*>::iterator it = errors_.begin();
       it != errors_.end(); it++) {
    delete (*it);
  }
}

void TestResult::AddListener(TestListener* listener)
{
  listeners_.push_back(listener);
}

void TestResult::RemoveListener(TestListener* listener)
{
  listeners_.remove(listener);
}

void TestResult::StartTest(Test* test)
{
  runCount_++;
  for (std::list<TestListener*>::iterator it = listeners_.begin();
       it != listeners_.end(); it++) {
    (*it)->StartTest(test);
  }
}

void TestResult::EndTest(Test* test)
{
  for (std::list<TestListener*>::iterator it = listeners_.begin();
       it != listeners_.end(); it++) {
    (*it)->EndTest(test);
  }
}

void TestResult::Reset()
{
  shouldStop_ = false;
}

void TestResult::Stop()
{
  shouldStop_ = true;
}

bool TestResult::ShouldStop() const
{
  return shouldStop_;
}

const std::list<TestFailure*>& TestResult::Failures() const
{
  return failures_;
}

const std::list<TestFailure*>& TestResult::Errors() const
{
  return errors_;
}

void TestResult::AddFailure(Test* test, const Exception& e)
{
  TestFailure* failure = new TestFailure(test, e.Message(), e.FileName(),
                                         e.Line());
  failures_.push_back(failure);
  for (std::list<TestListener*>::iterator it = listeners_.begin();
       it != listeners_.end(); it++) {
    (*it)->AddFailure(failure);
  }
}

void TestResult::AddError(Test* test, const Exception& e)
{
  TestFailure* error = new TestFailure(test, e.Message(), e.FileName(),
                                       e.Line());
  errors_.push_back(error);
  for (std::list<TestListener*>::iterator it = listeners_.begin();
       it != listeners_.end(); it++) {
    (*it)->AddError(error);
  }
}

int TestResult::RunCount() const
{
  return runCount_;
}

int TestResult::FailureCount() const
{
  return failures_.size();
}

int TestResult::ErrorCount() const
{
  return errors_.size();
}

bool TestResult::WasSuccessful() const
{
  return (!failures_.size() && !errors_.size());
}

///////////////////////////////////////////////////////////////////////////////
// TestFailure
///////////////////////////////////////////////////////////////////////////////
TestFailure::TestFailure(Test* test, const char* message, const char* fileName,
                         int line)
  : test_(test), message_(message), fileName_(fileName), line_(line)
{
}

TestFailure::~TestFailure()
{
}

Test* TestFailure::FailedTest() const
{
  return test_;
}

const char* TestFailure::What() const
{
  return message_.c_str();
}

const char* TestFailure::FileName() const
{
  return fileName_;
}

int TestFailure::Line() const
{
  return line_;
}

///////////////////////////////////////////////////////////////////////////////
// SimpleListener
///////////////////////////////////////////////////////////////////////////////
SimpleListener::SimpleListener()
{
}

SimpleListener::~SimpleListener()
{
}

void SimpleListener::StartTest(Test* test)
{
  fputc('.', stderr);
}

void SimpleListener::EndTest(Test* test)
{
}

void SimpleListener::AddFailure(const TestFailure* failure)
{
  fputs("F\n", stderr);
}

void SimpleListener::AddError(const TestFailure* failure)
{
  fputs("E\n", stderr);
}

///////////////////////////////////////////////////////////////////////////////
// Logger
///////////////////////////////////////////////////////////////////////////////
Logger::Logger(FILE *fp)
  : fp_(fp), hasError_(false)
{
}

Logger::~Logger()
{
  Close();
}

void Logger::StartTest(Test* test)
{
  hasError_ = false;
}

void Logger::EndTest(Test* test)
{
  if (!hasError_) PrintTestResult(test, true);
}

void Logger::AddFailure(const TestFailure* failure)
{
  if (!hasError_) {
    hasError_ = true;
    PrintTestResult(failure->FailedTest(), false);
  }
  fputs("Failure!!", fp_);
  TestUtil::PrintFailureDetail(fp_, failure);
}

void Logger::AddError(const TestFailure* error)
{
  if (!hasError_) {
    hasError_ = true;
    PrintTestResult(error->FailedTest(), false);
  }
  fputs("Error!!", fp_);
  TestUtil::PrintFailureDetail(fp_, error);
}

void Logger::PrintResult(const TestResult* result) const
{
  TestUtil::PrintResult(fp_, result);
}

void Logger::PrintTestResult(Test* test, bool ok) const
{
  fprintf(fp_, "[%s] %s\n", (ok ? "OK" : "NG"), test->GetName());
}

void Logger::PrintHeader() const
{
  fprintf(fp_, "-------------------\n");
  time_t t = time(0);
  struct tm* data = localtime(&t);
  char buf[80];
  memset(buf, 0, sizeof(buf));
  strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", data);
  fprintf(fp_, "%s\n\n", buf);
}

Logger* Logger::OpenFile(const char* fileName, const char* mode)
{
  FILE* fp = fopen(fileName, mode);
  return (fp ? new Logger(fp) : 0);
}

void Logger::Close()
{
  if (fp_ != 0 && fp_ != stdout && fp_ != stderr) {
    fclose(fp_);
  }
}

///////////////////////////////////////////////////////////////////////////////
// TestUtil
///////////////////////////////////////////////////////////////////////////////
void TestUtil::PrintFailureDetail(FILE* fp, const TestFailure* failure)
{
  fprintf(fp, " (%s:%d) %s\n", TrimFileName(failure->FileName()),
          failure->Line(), failure->What());
}

void TestUtil::PrintResult(FILE* fp, const TestResult* result)
{
  if (result->WasSuccessful()) {
    fprintf(fp, "\nOK (%d tests)\n", result->RunCount());
  } else {
    fprintf(fp, "\n!!!FAILURES!!!\n"
            "Runs: %d, Failures: %d, Errors: %d\n",
            result->RunCount(), result->FailureCount(), result->ErrorCount());
  }
}

const char* TestUtil::TrimFileName(const char* fileName)
{
  for (const char* p = fileName; *p != 0; p++) {
    if (*p == '/' || *p == '\\' || *p == ':') {
      fileName = p + 1;
    }
  }
  return fileName;
}
